17 指针的高级应用

17.1 动态存储分配

背景:c语言的数据结构通常是固定大小的,为了扩大数据结构的容量,必须修改程序并且再次编译。
说明(行为):在程序执行期间分配内存单元
用途:可以根据需要设计可以扩大(和缩小)的数据结构

适用类型 说明
字符串
数组
结构 可以链接成表、树和其它数据结构

17.1.1 内存分配函数

函数 说明 备注 返回值
malloc 分配内存快,但是不对内存块进行初始化 stdlib.h 最常用,不需要对分配的内存块进行清除,所以它比calloc更高效 void *(通用指针,本质上只是内存地址)
calloc 分配内存块,并且对内存块进行清除 stdlib.h
realloc 调整先前分配的内存块 stdlib.h

17.1.2 空指针

说明:“指向为空的指针”,这是一个区别于所有有效指针的特殊值。K&R CNULL(宏)来表示空指针。
相关场景:当调用内存分配函数时,如果无法定位满足我们需要的足够大的内存块,函数会返回空指针(null pointer)。

定义了NULL的库文件:

  1. locale.h
  2. stddef.h
  3. stdio.h
  4. stdlib.h
  5. string.h
  6. time.h

真假:所有非空指针都为真,而只有空指针为假。

1
2
3
4
5
6
7
if (p == NULL) ... 
<==>
if (!p) ...

if (p != NULL) ...
<==>
if (p) ...
1
2
3
4
p = malloc(10000);
if (p == NULL) {
/*分配内存失败,采取合适的措施*/
}

更酷的方式

1
2
3
if ((p = malloc(10000)) == NULL) {
/*分配内存失败,采取合适的措施*/
}

17.2 动态分配字符串

17.2.1 使用malloc函数为字符串分配内存

注意:为字符串分配内存空间时不要忘记包含空字符串的空间。

1
2
3
4
5
// 动态分配
char *p = (char *) malloc(n + 1);// malloc返回的通用指针会自动转化为char*型变量,因此强制类型转换的部分可以省略

// 赋值
strcpy(p, "abc");

17.2.2 在字符串函数中使用动态存储分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
* 拼接两个字符串并返回一个“新字符串”(不改变原有的两个字符串)
* @param s1 要拼接的字符串的第一部分
* @param s2 要拼接的字符串的第二部分
* @return 新字符串地址
*/

char *concat(const char *s1, const char *s2);

int main(int argc, char const *argv[]) {

printf("%s\n", concat("abc", "def")); // abcdef

return 0;
}

char *concat(const char *s1, const char *s2) {
// 定义指向新字符串的临时指针变量
char *result;

// 为新字符串分配空间
result = malloc(strlen(s1) + strlen(s2) + 1);

// 分配内存失败
if (result == NULL) {
printf("Error: malloc failed in concat \n");
exit (EXIT_FAILURE);
}

// 第一部分复制到新字符串的空间中(会有剩余)
strcpy(result, s1);

// 第二部分拼接到后面
strcat(result, s2);

return result;
}

17.2.3 动态分配字符串的数组

说明:在数组中存储字符串有两种方式。二维字符数组或者字符串字面量指针数组,相比之下,前者可能会浪费空间。

17.2.4 程序:显示一个月的提示列表(改进版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
* Prints a one-month reminder list
* 程序需要读入一系列天和提示的组合,并且按照顺序进行存储(按日期排序)
* 额外需求:
* 1. 天在两个字符的域中右对齐
* 2. 确定用户没有输入两位以上的数字
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_REMIND 50//备忘录数量
#define MSG_LEN 60//每条备忘的最大长度

int read_line(char str[], int n);

int main(){
//备忘录列表
char *reminders[MAX_REMIND];

//日期字符串和信息字符串
char day_str[3], msg_str[MSG_LEN + 1];

int day, i, j, num_remind = 0;

for(;;){
//如果备忘录已满,就停止循环
if(num_remind == MAX_REMIND){
printf("-- No space left --\n");
break;
}
//输入日期和一条备忘清单
printf("Enter day and reminder:");

//把日期读入到整型变量day中,即使输入更多的数字,在%与d之间的数2也会通知scanf函数在读入两个数字后停止
scanf("%2d", &day);

//如果日期输入0,则停止循环,不在输入任何备忘
if(day == 0){
break;
}

//把day的值转换为字符串并写到day_str中
sprintf(day_str, "%2d", day);//day_str会包含一个空字符结尾的合法字符串
read_line(msg_str, MSG_LEN);//读入备忘信息

//找到备忘录的位置(根据日期从小到大排序的位置)
for(i = 0; i < num_remind; i++){
//确定这一天的位置
if(strcmp(day_str, reminders[i]) < 0){
break;
}
}

//将包括该位置之后的备忘信息向后移动
for(j = num_remind; j > i; j--){
reminders[j], reminders[j-1];
}

reminders[i] = malloc(2 + strlen(msg_str) + 1);
if (reminders[i] == NULL) {
printf("-- No space left --\n");
break;
}
//将备忘信息放在空出来的位置
strcpy(reminders[i], day_str);

//将备忘信息拼接过去
strcat(reminders[i], msg_str);
num_remind++;
}

//打印出当前所有备忘信息
printf("\nDay Reminder\n");
for(i = 0; i < num_remind; i++){
printf("%s\n", reminders[i]);
}
return 0;
}

/**
* 读入一行
* @param str 存储字符串的位置
* @param n 字符串长度上限
* @return 该条字符串的长度
*/

int read_line(char str[], int n){
char ch;
int i = 0;

while((ch = getchar()) != '\n'){
if(i < n){
str[i++] = ch;
}
}
str[i] = '\0';
return i;
}
1
2
3
4
5
6
7
8
9
10
11
12
$ gcc -Wall -o remind remind.c

$ ./remind
Enter day and reminder:20 take money
Enter day and reminder:21 kill history
Enter day and reminder:19 find fulture
Enter day and reminder:0

Day Reminder
19 find fulture
20 take money
21 kill history

17.3 动态分配数组

原理:在程序执行期间为数组分配空间,然后通过指向数组第一个元素的指针访问数组。由于c语言中数组和指针的关系紧密,指向动态分配的内存块的指针可以当作数组的名字使用。
注意:计算数组所需的空间要使用 sizeof 运算符,如果分配空间不足,稍后往数组中存储时程序会出现异常。

17.3.1 使用malloc函数为数组分配存储空间

1
2
3
4
5
6
7
8
// 创建一个含n个整数的数组
int *a;
a = malloc(n * sizeof(int)); // 计算数组所需的空间要使用sizeof运算符

// 当作数组使用
for (i = 0; i < n; i++) {
a[i] = 0;
}

17.3.2 calloc函数

函数原型(stdlib.h):如果要求的空间无效,那么此函数返回空指针。

1
2
3
4
5
6
7
/**
* 分配内存空间并初始化
* @param {size_t} numeb 数组的长度
* @param {size_t} size 每个元素的大小(字节)
* @return {void *} 数组第一个元素的地址
*/

void *calloc(size_t nmeb, size_t size);

注意:

  • 当第一个参数为1时,可以为任何类型的数据项(不仅仅是数组)分配空间
  • calloc函数会清除分配的空间中的数据
1
2
3
4
5
6
7
8
9
// 声明一个长度为n的int型数组,并将所有项初始化为0
int *a = calloc(n, sizeof(int));

// 声明一个指向结构体的指针
struct point {
int x;
int y;
} *p;
p = calloc(1, sizeof(struct point)); //p将指向新创建的结构体,且结构体的成员x、y都为0

17.3.3 realloc函数

原型(stdlib.h):

1
2
3
4
5
6
7
/**
* 调整分配的内存的大小
* @param {void *} ptr 指向内存块(通常是数组)的指针
* @param {size_t} size 内存块的新尺寸
* @return {void *} 新的内存块的地址
*/

void *realloc(void *ptr, size_t size);

用途:一旦为数组分配完内存,稍后可能会出现数组过大或过小。relloc函数可以调整数组的大小以使它更适合需要。
局限:要确定传递给realloc函数的指针来自于先前malloc函数calloc函数realloc函数的调用获得的。否则程序会出现异常。
规则:

  • 如果无法扩大内存(后边内存被占用),会在别处分配新的内存,然后把旧块中的内容复制过去
  • 当扩展内存块时, realloc函数不会对添加进内存块的字节进行初始化
  • 如果realloc函数不能按要求扩大内存块,那么它会返回空指针,并且在原有的内存块中的数据不会发生改变。
  • 如果realloc函数调用时以空指针作为第一个实际参数,那么它的行为就将像malloc函数一样
  • 如果realloc函数调用时以0作为第二个实际参数,那么它会释放掉内存块

注意:一旦realloc函数返回,一定要对指向内存块的所有指针进行更新(将新的地址赋值给指针),因为可能realloc函数移动到了其它地方的内存块。

17.4 释放存储

堆(heap):malloc函数和其他内存分配函数所获得的内存块都来自一个称为堆的存储池。调用这些函数经常会耗尽堆,或者要求大的内存块也会耗尽堆,这会导致函数返回空指针。
垃圾(garbage):对程序而言不再访问到的内存块被称为垃圾。
内存泄漏(memroy leak):运行中留有垃圾被称为内存泄漏。
垃圾收集器(garbage collector):用于垃圾的自动定位和回收,但c语言不提供。相反,每个c程序负责回收各自的垃圾(调用free函数)。

1
2
3
4
/*模拟内存泄漏*/
p = malloc(...)
q = malloc(...)
p = q; // p原本指向的内存块变成垃圾

17.4.1 free函数

用途:调用free函数将内存块释放返回堆。
原型:stdlib.h
限制:free函数的实际参数必须是指针,而且一定是先前内存分配函数返回的。

1
2
3
4
5
/**
* 释放内存
* @param {void *} ptr 指向需要释放的内存块的指针
*/

void free(void *ptr);

17.4.2 “悬空指针”问题

悬空指针(dangling pointer):指向被free掉的内存块的指针。
注意:悬空指针很难被发现,而且试图通过“悬空指针”修改被释放掉的内存块会导致程序异常。

17.5 链表

链表(linked list):是由一连串的结构(节点)组成的,其中每个节点都包含指向下一个链中节点的指针。
优点:更灵活,方便扩大和缩小(插入和删除)。
缺点:没有“随机访问”的能力

17.5.1 声明节点类型

注意:结点类型只能使用标记而不能使用typedef定义结构,因为后者无法在节点内声明指向另一个结点的成员。

1
2
3
4
5
6
7
// 简单的单个节点的结构
struct node {
int value;
struct node *next; // 指向下一个节点
};

struct node *first = NULL; //链表初始为空

17.5.2 创建节点

1
2
3
4
5
6
7
8
// 声明节点
struct node *new_node;

// 为节点分配内存
new_node = malloc(sizeof(struct node));

// 为节点初始化值
(*new_node).value = 0; // .的优先级高于间接寻址运算符*,所以使用()提升后者优先级

17.5.3 ->运算符

右箭头选择(right arrow selection):通过指针访问结构中的成员

1
scanf("%d", &new_node->value); //scanf("%d", &(*new_node).value)

17.5.4 在链表的开始处插入节点

步骤

  1. 为节点分配内存单元
  2. 把数据存储在节点中
  3. 把节点插入到链表中

伏笔:在17.6节中对add_to_list有进一步优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <stdio.h>
#include <stdlib.h>
#define EXIT_FALURE 0
/**
* @brief 节点
* @struct
*/

struct node {
int value;
struct node *next; // 指向下一个节点
};

/**
* 添加节点到链表头部
* 需要注意的是,该函数执行后还需要将头部指向该函数返回的新的节点才能完成插入到链表头部的工作
* @param list 要插入的链表(指向头部节点的指针)
* @param n 要插入的节点存储的值
* @return 新的链表(指向新的头节点的指针)
*/

struct node *add_to_list (struct node *list, int n) {
// 声明新节点
struct node *new_node;

// 为新节点分配内存
new_node = malloc(sizeof(struct node));

if (new_node == NULL) {
printf("Error: malloc failed in add_ro list\n");
exit(EXIT_FALURE);
}

new_node->value = n;
new_node->next = list;
return new_node;
}

/**
* 通过命令行完成链表的创建
* @return 链表的头部
*/

struct node *read_numbers(void) {
struct node *first = NULL;
int n;

printf("Enter a series of intergers (0 to terminate):\n");

for (;;) {
scanf("%d", &n);
if (n == 0) {
return first;
}
first = add_to_list(first, n);
}
}

int main () {
// 创建一个含有用户录入的数字的链表
struct node *num_list;
num_list = read_numbers();
}

17.5.5 搜索链表

惯用法:for (p = first; p != NULL; p = p->next)
形式一:惯用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 根据存储的值寻找节点
* @param {struct node *} list 链表(头部指针)
* @param {int} n 目标节点存储的值
* @return 目标节点的指针或NULL
*/

struct node *search_list(struct node *list, int n) {
struct node *p;
for (p = list; p != NULL; p = p->next) {
if (p->value == n) {
return p;
}
}
return NULL;
}

形式二:省略中间变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 根据存储的值寻找节点
* @param {struct node *} list 链表(头部指针)
* @param {int} n 目标节点存储的值
* @return 目标节点的指针或NULL
*/

struct node *search_list(struct node *list, int n) {
for (; list != NULL; list = list->next) {
if (list->value == n) {
return list;
}
}
return NULL;
}

形式三:链表到末尾和找到目标判定合并

1
2
3
4
5
6
7
8
9
10
11
/**
* 根据存储的值寻找节点
* @param {struct node *} list 链表(头部指针)
* @param {int} n 目标节点存储的值
* @return 目标节点的指针或NULL
*/

struct node *search_list(struct node *list, int n) {
for (; list != NULL && list->value != n; list = list->next)
;
return list;
}

形式四:使用while

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 根据存储的值寻找节点
* @param {struct node *} list 链表(头部指针)
* @param {int} n 目标节点存储的值
* @return 目标节点的指针或NULL
*/

struct node *search_list(struct node *list, int n) {
wile (list != NULL && list->value != n) {
list = list->next
}
return list;
}

17.5.6 从链表中删除节点

步骤

  1. 定位要删除的节点
  2. 改变前一个节点,从而使它“绕过”删除节点
  3. 调用free函数从而收回删除节点占用的内存空间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 根据节点的值找到节点并删除之
* @param {struct node*} list 所在的链表
* @param {int} n 要删除的节点存储的值
* @return {struct node*} 链表的头节点
*/

struct node *delete_from_list (struct node *list, int n) {
struct node *cur, *prev;
// 定位要删除的节点
for (cur = list, prev = NULL; cur != NULL && cur->value != n; prev = cur, cur = cur->next)
;
// 没有找到要删除的节点
if (cur == NULL) {
return list;
}
// 找到了,要删除的节点是第一个节点
if (prev == NUL) {
list = list->next;
}
// 找到了, 要删除的节点不是第一个几点
else {
prev->next = cur->next;
}
free(cur);
return list;
}

17.5.8 程序:维护零件数据库(改进版)

说明:使用链表代替数组有两个主要的好处

  1. 不需要事先限制数据库的大小,数据库可以扩大到没有更多内存空间存储零件为止
  2. 可以很容易保持用零件编号排序的数据库,当往数据库中添加新零件时,只是简单把它插入链表中的适当位置就可以了
1
2
3
4
5
6
7
8
9
10
$ make
$ tree -L 2
.
├── invent2
├── invent2.c
├── invent2.o
├── makefile
├── readline.c
├── readline.h
└── readline.o └── readline.o

readline.h

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef READLINE_H
#define READLINE_H
#endif
/**
* 读入一行
* 会跳过开头的空白符
*
* @param str 读入的内容
* @param n 字符串的内容
* @return 读入字符的个数
*/

int read_line(char str[], int n);

readline.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <ctype.h>
#include <stdio.h>
#include "readline.h"

/**
* 读入一行
* 会跳过开头的空白符
*
* @param str 读入的内容
* @param n 字符串的内容
* @return 读入字符的个数
*/

int read_line(char str[], int n) {
// ch的类型为int而不是char,便于判定EOF
int ch, i =0;
// 跳过所有空白符
while (isspace(ch = getchar()))
;
while (ch != '\n' && ch != EOF) {
if (i < n) {
str[i++] = ch;
}
ch = getchar();
}
str[i] = '\0';
return i;
}

invent2.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/**
* Maintains a parts database (linked list version)
*/

#include <stdio.h>
#include <stdlib.h>
#include "readline.h"

#define NAME_LEN 25
/**
* 定义零件
*/

struct part {
int number; // 编号
char name[NAME_LEN + 1]; // 名字长度
int on_hand; // 当前库存
struct part *next; // 指向下一个零件
};

// 链表(头节点)
struct part *inventory = NULL;

struct part *find_part (int number);
void insert (void);
void search (void);
void update (void);
void print (void);

int main(){
char code;
for (;;) {
printf("Enter operation code:");
scanf(" %c", &code);
// 跳过换行符
while (getchar() != '\n')
;
switch (code) {
case 'i': insert();
break;
case 's': search();
break;
case 'u': update();
break;
case 'p': print();
break;
case 'q': return 0;
default: printf("Illegal code\n");
}
printf("\n");
}
}

/**
* 搜索零件
* @param number 零件包含的值
* @return 对应零件节点的地址(没找到返回NULL)
*/

struct part *find_part (int number) {
struct part *p;
for (p = inventory; p != NULL && number > p->number; p = p ->next)
;
if (p != NULL && number == p->number) {
return p;
}
return NULL;
}

/**
* 插入一种零件
*/

void insert (void) {
struct part *cur, *prev, *new_node;
// 为新节点分配空间
new_node = malloc(sizeof(struct part));
if (new_node == NULL) {
printf("Database is full; can't add more parts.\n");
return;
}
// 零件编号
printf("Enter part number:");
scanf("%d", &new_node->number);

// 根据编号寻找插入位置(按从小到大的顺序排列)
for (cur = inventory, prev = NULL; cur != NULL && new_node->number > cur->number; prev = cur, cur = cur->next)
;

// 发现编号相同的节点
if (cur != NULL && new_node->number == cur->number) {
printf("Part already exits.\n");
free(new_node);
return;
}

// 零件名
printf("Enter part name:");
read_line(new_node->name, NAME_LEN);

// 零件数量
printf("Enter quantity on hand:");
scanf("%d", &new_node->on_hand);

// 插入进去
new_node->next = cur;
if (prev == NULL) {
inventory = new_node;
}
else {
prev->next = new_node;
}
}

/**
* 以交互的方式根据编号搜索并显示目标零件
*/

void search (void) {
int number;
struct part *p;
printf("Enter part number:");
scanf("%d", &number);
p = find_part(number);
if (p != NULL) {
printf("Part name: %s\n", p->name);
printf("Quantity on hand: %d\n", p->on_hand);
}
else {
printf("Part not found.\n");
}
}

/**
* 修改零件
*/

void update (void) {
int number, change;
struct part *p;

printf("Enter part number:");
scanf("%d", &number);
p = find_part(number);
if (p != NULL) {
printf("Enter change in quantity on hand:");
scanf("%d", &change);
p->on_hand += change;
}
else {
printf("Part not found.\n");
}
}

/**
* 打印所有零件
*/

void print (void) {
struct part *p;
printf("Part number Part Name Quantity on hand\n");
for (p = inventory; p != NULL; p = p->next) {
printf("%7d %-25s%11d\n", p->number, p->name, p->on_hand);
}
}

17.6 指向指针的指针

说明:对17.5.4中的add_to_list进行优化,优化后插入链表的功能将完全由该函数提供。
原理:通过指针的指针的副本,达到修改指针指向的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 向链表中插入节点
* @param {struct node **} node 指向链表的头节点的指针的指针
* @param {int} n 节点存储的值
*/

void add_to_list (struct node **list, int n) {
struct node *new_node;
new_node = malloc(sizeof(struct node));
if (new_node == NULL) {
printf("Error: malloc failed in add_to_list\n");
exit (EXIT_FAILURES);
}
new_node->value = n;
// 新节点的下一个节点指向链表头节点
new_node->next = *list;

// 原本指向头节点的指针指向新节点
// 详解:list的值是first这个指针本身的地址,通过*list便可以访问到first这个指针
*list = new_node;
}
...
add_to_list(&first, 10);

17.7 指向函数的指针

说明:毕竟函数占用内存单元,所以每个函数都有地址,就像每个变量都有地址一样。

17.7.1 函数指针作为实际参数

声明:声明为指向函数的指针有两种方式,从编译器的角度看是完全一样的。

方式一:返回值 函数名(返回值 (*函数名)(参数1, 参数2, ...), 参数)

1
2
3
4
double integrate (double (*f)(double), double a, double b)  {
...
sum += (*f)(x); // 或者sum += f(x)
}

方式二:返回值 函数名(返回值 (函数名)(参数1, 参数2, ...), 参数)

1
2
3
4
double integrate (double f(double), double a, double b) {
...
sum += (*f)(x);// 或者sum += f(x)
}

17.7.2 qsort函数

原型:stdlib.h

1
2
3
4
5
6
7
/**
* 为数组排序
* @param {void *} base 指向数组需要排序的部分的第一个元素
* @param {size_t} nmemb 要排序的元素的数量
* @param {int (*)} compare 指向排序函数的指针
*/
void qsort (void *base, size_t nmemb, size_t size, int (*compar) (const void *, const void *));
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 比较函数(提供给qsort函数排序规则)
* @param {void *} p 第一个零件
* @param {void *} q 第二个零件
* @return 正数(1):*p > *q;负数(-1):*p < *q;零(0):*p = *q
*/

int compare_parts (const void *p, const void *q) {
return ((struct part *) p)->number - ((struct part *) q)->number;
}

// 使用比较函数进行排序
qsort(inventory, num_parts, sizeof(struct part), compare_parts);

17.7.3 函数指针的其他用途

说明:c语言对待指向函数的指针就像对待指向数据的指针一样。我们可以把函数存储在变量中,或者用做数组的元素,再或者用做结构或联合的成员,甚至可以编写返回函数指针的函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*存储函数的变量*/
void (*pf) (int); //声明一个可以存储指向函数的指针的变量(pf可以指向任何带有int型实际参数,且返回值为void的函数)

pf = f; // 指向函数f

(*pf)(i); // pf(i)


/*存储函数的数组*/
void (*file_cmd[])(void) = {
new_cmd,
open_cmd,
close_cmd,
close_all_cmd,
save_cmd,
ext_cmd
};
(*file_cmd[n])(); // 或者file_cmd[n]();

17.7.4 程序:列三角函数表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* Tabulates values of trigonometric functions
*/
#include <math.h>
#include <stdio.h>

void tabulate (double (*f)double, double first, double last, double incr);

int main () {
double final, increament, initial;
printf("Enter initial value: ");
scanf("%lf", &initial);

printf("Enter final value:");
scanf("%lf", &final);

printf("Enter increament:");
scanf("%lf", &increament);

printf("\n x cos(x)\n ------- -------\n");
tabulate(cos, initial, final, increament);

printf("\n x sin(x)\n ------- -------\n");
tabulate(sin, initial, final, increament);

printf("\n x tan(x)\n ------- -------\n");
tabulate(tan, initial, final, increament);

return 0;
}

void tabulate (double (*f)(double), double first, double last, double incr) {
double x;
int i, num_intervals;
num_intervals = cell((last - first) / incr);
for (i = 0; i <= num_intervals; i++) {
x = first + i * incr;
printf("%10.5f %10.5f\n", x, (*f)[x]);
}
}